package digitRecognitionProblem.reduceConnections;

import genetic_algorithm.Chromosome;
import genetic_algorithm.Crossover;
import genetic_algorithm.Mutation;

import java.util.AbstractMap;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import utils.RandomGenerator;

public class ReduceConCrossover implements Crossover {

	public static final int SINGLE_POINT = 0; // single point crossover
	private int mode; // crossover mode
	
	/**
	 * Constructor- creates a new object implementing crossover phase for the
	 * queens problem. Crossover is performed according to given crossover mode:
	 * single point, double point, uniform or smart
	 * @param mode mode for the crossover
	 */
	public ReduceConCrossover(int mode) {
		// set crossover mode
		this.mode = mode;
	}
	
	/**
	 * Gets a pair of parent chromosomes. In chance of crossover-rate, mates
	 * parents (according to the mode which was specified during the
	 * initialization) and mutates them in chance of mutation-rate. If crossover
	 * is not performed, parents are not changed and returned as they are
	 */
	@Override
	public Entry<Chromosome, Chromosome> mate(
			Entry<Chromosome, Chromosome> parents, double crossoverRate,
			Mutation mutator, double mutationRate) {

		
		// skip crossover in chance of crossover-rate
		if ((RandomGenerator.nextInt(1000) / 1000.0) > crossoverRate) {
			return parents;
		}

		// perform crossover according to selected mode
		List<Chromosome> offsprings = null;
		switch (mode) {
		case SINGLE_POINT: {
			offsprings = singlePoint(parents.getKey(), parents.getValue());
			break;
		}

		default:
			break;
		}

		// mutate the offsprings
		mutator.mutate(offsprings, mutationRate);

		// return resulting offsprings
		return new AbstractMap.SimpleEntry<Chromosome, Chromosome>(
				offsprings.get(0), offsprings.get(1));
	}

	/**
	 * Performs crossover on given parent chromosomes in a single-point manner:
	 * randomly picks an index in the parents' list values; generates first
	 * offspring by concatenating father's prefix with mother's postfix;
	 * generates second offspring by concatenating mother's prefix with father's
	 * postfix
	 * @param father first parent chromosome
	 * @param mother second parent chromosome
	 * @return list containing offsprings chromosmes
	 */
	private List<Chromosome> singlePoint(Chromosome father, Chromosome mother) {
		
		// randomly choose split index
		int numValues = ReduceConChromosome.CHROM_LEN;
		int splitIndex = RandomGenerator.nextInt(numValues);

		// initialize values list for offspring chromosomes (all with 0's)
		BitSet firstOffspringVals = new BitSet(numValues);
		BitSet secondOffspringVals = new BitSet(numValues);

		// get binary data of each parent
		BitSet fData = ((ReduceConChromosome) father).getData();
		BitSet mData = ((ReduceConChromosome) mother).getData();
		
		// extract prefix and postfix of chromosomes' values
		BitSet fatherPrefix = (BitSet) fData.clone();
		fatherPrefix.clear(splitIndex, numValues);
		BitSet fatherPostfix = (BitSet) fData.clone();
		fatherPostfix.clear(0,splitIndex);
		BitSet motherPrefix = (BitSet) mData.clone();
		motherPrefix.clear(splitIndex, numValues);
		BitSet motherPostfix = (BitSet) mData.clone();
		motherPostfix.clear(0,splitIndex);
		
		// concatenate father's prefix with mother's postfix
		firstOffspringVals.or(fatherPrefix);
		firstOffspringVals.or(motherPostfix);

		// concatenate mother's prefix with father's postfix
		secondOffspringVals.or(motherPrefix);
		secondOffspringVals.or(fatherPostfix);

		// generate chromosomes out of offsprings values		
		ReduceConChromosome firstOffspring = new ReduceConChromosome(firstOffspringVals);
		ReduceConChromosome secondOffspring = new ReduceConChromosome(secondOffspringVals);

		// return resulting chromosomes
		List<Chromosome> offsprings = new LinkedList<Chromosome>();
		offsprings.add(firstOffspring);
		offsprings.add(secondOffspring);
		return offsprings;
	}
}
